package cc.verywell.pureblock.msg.nfs.strc;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;

import cc.verywell.pureblock.util.unsafe.UnsafeRun;

/**
 *<h1>File Structure V0</h1>
* <pre>
* [byte_1] head;{
*   & ____0001 datas:
*     [byte_2] datasArray.length
*     {
*       [byte_8] datas.totalsize
*       [byte_2] length = (link.hash+link.priority)
*       {
*         [byte_rest] datalink
*       }
*     }
*   & ____0010 alias:
*     [bit_2] aliasArray.length
*     {
*       [bit_2] alias.length
*       {
*         [bit_rest] alias
*       }
*     }
*   & ____0100 signs:
*     [bit_2] signsArray.length
*     {
*       [bit_2] pubKey.length
*       {
*         [bit_rest] pubKey
*       }
*       [bit_2] sign.length
*       {
*         [bit_rest] sign
*       }
*     }
*   & ___1000 links:
*     [byte_2] datasArray.length
*     {
*       [bit_1] link.priority
*       [bit_2] length = (link.hash+link.priority)
*       {
*         [bit_rest] link
*       }
*     }
*    & 01110000 >>> 3 unUsedHead
*    & 10000000 unSupported : throw new Err;
* }
* [bit_rest] data;
* 
* </pre>
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年10月2日 上午4:57:31
*/
public class NfsStructureV1 implements EncodeAble{
	final public static int STRUCTURE_HEAD_SIZE = 1;

	final private byte[] payload;
	final public Sub data;
	final public DataLink[] datas;
	final public Sub[] alias;
	final public Sign[] signs;
	final public DataLink[] links;
	//final public ExtraLink[] links;
	final public byte unUsedHead;
	
	/**
	 * @return bb = new Bytebuffer ();
	 * <br>bb.position = dataOffset; 
	 * <br>bb.limit = dataLen+dataOffset;
	 * <br>bb.capacity = payload.length;
	 */
	public interface Sub extends EncodeAble{
		public int getLength();
		public byte[] getHash();
		public ByteBuffer getByteBuffer();
		final public static int ARRAY_HEAD_SIZE = 2;
	}
	public interface DataLink extends Sub{
		public long getSize();
		public static int SINGLE_EXTRA_SIZE = 2+8;
		final public static int ARRAY_HEAD_SIZE = 2;
	}
//	public interface ExtraLink extends Sub{
//		public byte getPriority();
//		final public static int SINGLE_EXTRA_SIZE = 2+1;
//		final public static int ARRAY_HEAD_SIZE = 2;
//	}
	static public class Sign implements EncodeAble{
		private Sub pubKey;
		private Sub sign;
		public Sign(Sub pubKey,Sub sign) {
			this.pubKey = pubKey;
			this.sign = sign;
		}
		public Sub getPubKey() {
			return pubKey;
		}
		public Sub getSign() {
			return sign;
		}
		@Override
		public int encodeSize() {
			return pubKey.encodeSize()+sign.encodeSize();
		}
		@Override
		public void encode(ByteBuffer bb) {
			pubKey.encode(bb);
			sign.encode(bb);
		}
		final public static int ARRAY_HEAD_SIZE = 2;
	}
	//////Read
	public long readMemoryUsed() {
		long l = 0;
		l += 40+UnsafeRun.MemoryAddressSize*5;
		l += ((12+payload.length-1)/8+1)*8;
		if(data!=null) l += 32;
		if(datas!=null) l += datas.length*(UnsafeRun.MemoryAddressSize+64)+16;
		if(links!=null) l += links.length*(UnsafeRun.MemoryAddressSize+64/*48*/)+16;
		if(alias!=null) l += alias.length*(UnsafeRun.MemoryAddressSize+32)+16;
		if(signs!=null) l += signs.length*(UnsafeRun.MemoryAddressSize+8)+signs.length*(UnsafeRun.MemoryAddressSize+32)*2+16;
		return l;
	}
	public long dataSize() {
		long l = 0;
		if(data!=null)l = data.getLength();
		if(datas!=null) {
			for (DataLink d : datas) {
				l += d.getSize();
			}
		}
		return l;
	}
	public boolean isPuredata() {
		if(this.payload!=null && this.payload.length>0)
			return this.payload[0]==0;
		else
			return datas==null && alias==null && signs==null && links==null && unUsedHead==0;
	}
	public NfsStructureV1(byte[] payload) {
		this.payload = payload;
		if(payload==null)throw new NullPointerException();
		ByteBuffer bb = ByteBuffer.wrap(payload);
		byte dataHead = bb.get();
		{
			///分析主体
			if(0!=(0b10000000 & dataHead))throw new IllegalArgumentException(Integer.toBinaryString(dataHead));
			
			if(0!=(0b00000001 & dataHead)) {
				int i = 0xFFFF & bb.getShort();
				DataLinkRead[] sub = new DataLinkRead[i];
				for(i=0;i<sub.length;i++) {
					long size = bb.getLong();
					int j = 0xFFFF & bb.getShort();
					int pos = bb.position();
					bb.position(pos+j);
					sub[i] = new DataLinkRead(pos,j,size);
				}
				datas = sub;
			}else {
				datas = null;
			}
			if(0!=(0b00000010 & dataHead)) {
				int i = 0xFFFF & bb.getShort();
				SubRead[] sub = new SubRead[i];
				for(i=0;i<sub.length;i++) {
					int j = 0xFFFF &  bb.getShort();
					sub[i] = new SubRead(bb.position(),j);
					bb.position(bb.position()+j);
				}
				alias = sub;
			}else {
				alias = null;
			}
			if(0!=(0b00000100 & dataHead)) {
				int i = 0xFFFF &  bb.getShort();
				Sign[] sub = new Sign[i];
				for(i=0;i<sub.length;i++) {
					int j;
					j = 0xFFFF & bb.getShort();
					SubRead pubKey = new SubRead(bb.position(),j);
					bb.position(bb.position()+j);
					j = 0xFFFF & bb.getShort();
					SubRead sign = new SubRead(bb.position(),j);
					bb.position(bb.position()+j);
					sub[i] = new Sign(pubKey,sign);
				}
				signs = sub;
			}else {
				signs = null;
			}
			if(0!=(0b00001000 & dataHead)) {
				int i = 0xFFFF & bb.getShort();
				DataLinkRead[] sub = new DataLinkRead[i];
				for(i=0;i<sub.length;i++) {
					long size = bb.getLong();
					int j = 0xFFFF & bb.getShort();
					int pos = bb.position();
					bb.position(pos+j);
					sub[i] = new DataLinkRead(pos,j,size);
				}
				links = sub;
			}else {
				links = null;
			}
			if(bb.hasRemaining()) {
				data = new SubDataRead(bb.position(), bb.remaining());
			}else {
				data = null;
			}
			unUsedHead = (byte)((0b01110000 & dataHead) >>> 4);
		}
	}
	public class SubRead implements Sub{
		protected int offset;
		protected int len;
		public int getLength() {
			return len;
		}
		private byte[] buf;
		public byte[] getHash() {
			byte[] bb = buf;
			if(bb!=null)return bb;
			bb = Arrays.copyOfRange(payload, offset, offset+len);
			buf = bb; 
			return bb;
		}
		public ByteBuffer getByteBuffer() {
			if(len==0)return null;
			return ByteBuffer.wrap(payload, offset, len);
		}
		public SubRead(int offset, int len) {
			this.offset = offset;
			this.len = len;
		}
		@Override
		public int encodeSize() {
			return 2+len;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putShort((short)len);
			bb.put(payload, offset, len);
		}
	}
	public class SubDataRead extends SubRead{
		public SubDataRead(int offset, int len) {
			super(offset, len);
		}
		@Override
		public int encodeSize() {
			return len;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.put(payload, offset, len);
		}
	}
//	public class ExtraLinkRead extends SubRead implements ExtraLink{
//		protected byte priority;
//		public ExtraLinkRead(int offset, int len, byte priority) {
//			super(offset, len);
//			this.priority = priority;
//		}
//		public byte getPriority() {
//			return this.priority;
//		}
//		@Override
//		public int encodeSize() {
//			return ExtraLink.SINGLE_EXTRA_SIZE+len;
//		}
//		@Override
//		public void encode(ByteBuffer bb) {
//			bb.put(priority);
//			bb.putShort((short)(1+len));
//			bb.put(payload, offset, len);
//		}
//	}
	public class DataLinkRead extends SubRead implements DataLink{
		protected long size;
		public DataLinkRead(int offset, int len, long size) {
			super(offset, len);
			this.size = size;
		}
		@Override
		public long getSize() {
			return this.size;
		}
		@Override
		public int encodeSize() {
			return DataLink.SINGLE_EXTRA_SIZE+len;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putLong(size);
			bb.putShort((short)(len));
			bb.put(payload, offset, len);
		}
	}

	
	////write
	@Override
	public int encodeSize() {
		return payload.length;
	}
	@Override
	public void encode(ByteBuffer bb) {
		bb.put(payload);
	}
	//static
	static public void encode(ByteBuffer bb, byte[] data, List<DataLink> datalinks, List<Sub> alias, List<Sign> signs, List<DataLink> extralinks, byte unUsedHead) {
		//TODO
		int head = 0;
		if(datalinks!=null)head |= 0b00000001;
		if(alias!=null)head |= 0b00000010;
		if(signs!=null)head |= 0b00000100;
		if(extralinks!=null)head |= 0b00001000;
		if(unUsedHead!=0)head |= ((0xF & unUsedHead)<<3);
		bb.put((byte)head);
			
		if(datalinks!=null){
			bb.putShort((short)datalinks.size());
			for (EncodeAble ea : datalinks)ea.encode(bb);
		}
		if(alias!=null){
			bb.putShort((short)alias.size());
			for (EncodeAble ea : alias)ea.encode(bb);
		}
		if(signs!=null){
			bb.putShort((short)signs.size());
			for (EncodeAble ea : signs)ea.encode(bb);
		}
		if(extralinks!=null){
			bb.putShort((short)extralinks.size());
			for (EncodeAble ea : extralinks)ea.encode(bb);
		}
		if(data!=null){
			bb.put(data);
		}
	}
	static public int encodeSize(int dataSize, List<DataLink> datalinks, List<Sub> alias, List<Sign> signs, List<DataLink> extralinks) {
		int l = dataSize+1; //1 for headcode
		//if(data!=null)l+=data.encodeSize();
		if(datalinks!=null){
			l+= DataLink.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : datalinks)l+=ea.encodeSize();
		}
		if(alias!=null){
			l+= Sub.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : alias)l+=ea.encodeSize();
		}
		if(signs!=null){
			l+= Sign.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : signs)l+=ea.encodeSize();
		}
		if(extralinks!=null){
			l+= DataLink.ARRAY_HEAD_SIZE;
			for (EncodeAble ea : extralinks)l+=ea.encodeSize();
		}
		return l;
	}
	static public class SubWrite implements Sub{
		public int getLength() {
			return buf.length;
		}
		final protected byte[] buf;
		public byte[] getHash() {
			return buf;
		}
		public ByteBuffer getByteBuffer() {
			return ByteBuffer.wrap(buf);
		}
		public SubWrite(byte[] buf) {
			this.buf = buf;
		}
		@Override
		public int encodeSize() {
			return 2+buf.length;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putShort((short)buf.length);
			bb.put(buf);
		}
	}
//	static public class ExtraLinkWrite extends SubWrite implements ExtraLink{
//		final protected byte priority;
//		public ExtraLinkWrite(byte[] buf, byte priority) {
//			super(buf);
//			this.priority = priority;
//		}
//		public byte getPriority() {
//			return this.priority;
//		}
//		@Override
//		public int encodeSize() {
//			return ExtraLink.SINGLE_EXTRA_SIZE+buf.length;
//		}
//		@Override
//		public void encode(ByteBuffer bb) {
//			bb.put(priority);
//			bb.putShort((short)buf.length);
//			bb.put(buf);
//		}
//	}
	static public class DataLinkWrite extends SubWrite implements DataLink{
		final protected long size;
		public DataLinkWrite(byte[] buf, long size) {
			super(buf);
			this.size = size;
		}
		@Override
		public long getSize() {
			return size;
		}
		@Override
		public int encodeSize() {
			return DataLink.SINGLE_EXTRA_SIZE+buf.length;
		}
		@Override
		public void encode(ByteBuffer bb) {
			bb.putLong(size);
			bb.putShort((short)buf.length);
			bb.put(buf);
		}
	}
	
}
